package com.mxchen.lottery.domain.strategy.service.algorithm;

import cn.hutool.core.util.RandomUtil;
import com.mxchen.lottery.domain.strategy.model.AwardRateInfo;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author : hmxchen
 * @program : lottery-964-mxchen
 * @description : 共用的抽奖算法逻辑
 * 散列算法的宗旨就是：构造冲突较低的散列地址，保证散列表中数据的离散度
 * @create : 2021-12-14 10:52
 **/
public abstract class BaseAlgorithm implements IDrawAlgorithm{
    /**
     * 斐波那契散列增量
     * 逻辑：黄金分割点：(√5 - 1) / 2 = 0.6180339887，
     * Math.pow(2, 32) * 0.6180339887 = 0x61c88647
     */
    private final int HASH_INCREMENT = 0x61c88647;
    /**
     * 数组初始化长度
     * 保证数据填充时不发生碰撞的最小初始化值
     * 128 是通过斐波那契计算索引存放 100 个数的最小2的n次幂空间
     */
    private final int RATE_TUPLE_LENGTH = 128;
    /**
     * 存放概率与奖品对应的散列结果，strategyId -> rateTuple
     */
    protected Map<Long, String[]> rateTupleMap = new ConcurrentHashMap<>();
    /**
     * 奖品区间概率值，strategyId -> [awardId->begin、awardId->end]
     */
    protected Map<Long, List<AwardRateInfo>> awardRateInfoMap = new ConcurrentHashMap<>();

    @Override
    public void initRateTuple(Long strategyId, List<AwardRateInfo> awardRateInfoList) {
        // 保存奖品概率信息
        awardRateInfoMap.put(strategyId, awardRateInfoList);
        //若strategyId对应的value为空，会将第二个参数的返回值存入并返回
        String[] rateTuple = rateTupleMap.computeIfAbsent(strategyId, k -> new String[RATE_TUPLE_LENGTH]);
        int cursorVal = 0;
        for (AwardRateInfo awardRateInfo : awardRateInfoList) {
            //概率：100
            int rateVal = awardRateInfo.getAwardRate().multiply(new BigDecimal(100)).intValue();
            // 循环填充概率范围值
            for (int i = cursorVal + 1; i <= (rateVal + cursorVal); i++) {
                //hashIdx(i) 取1-100 对应1-128 的坐标
                rateTuple[hashIdx(i)] = awardRateInfo.getAwardId();
            }

            cursorVal += rateVal;

        }
    }

    @Override
    public boolean isExistRateTuple(Long strategyId) {
        return rateTupleMap.containsKey(strategyId);
    }
    /**
     * 斐波那契（Fibonacci）散列法，计算哈希索引下标值
     * 来源：https://www.cnblogs.com/xiaofuge/p/14291261.html
     * 斐波那契（Fibonacci）散列法
     *
     * 和平方散列法类似，此种方法使用斐波那契数列的值作为乘数而不是自己。
     *
     *     对于 16 位整数而言，这个乘数是 40503  0.6180339887*2^16=40503.4754834432
     *     对于 32 位整数而言，这个乘数是 2654435769。 0.6180339887*2^32=2654435769.2829335552
     *     对于 64 位整数而言，这个乘数是 11400714819323198485。0.6180339887*2^64=11400714818402800990.5250107392
     *
     * 公式：f(k) = ((k * 2654435769) >> X) << Y。对于常见的32位整数而言，也就是f(k) = (k * 2654435769) >> 28。
     * @param val 值
     * @return 索引
     */
    protected int hashIdx(int val) {
        int hashCode = val * HASH_INCREMENT + HASH_INCREMENT;
        return hashCode & (RATE_TUPLE_LENGTH - 1);
    }

    protected int getRandom(){
        //随机0-99 ,我们需要的是1-100 ,所以需要加1;
        return RandomUtil.getSecureRandom().nextInt(100)+1;
    }
}
